﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using LiSA_Server.Base.AbstractActioners;
using LiSA_Server.Base.Factories;
using LiSA_Server.Base.NHibernate;
using LiSA_Service.ResearchProjects.Dto;
using LiSA_Service.ResearchProjects.Dto.Commands;
using LiSA_Service.ResearchProjects.Dto.Queries;
using LiSA_Service.ResearchProjects.Models;
using LiSA_Service.Users.Actioners;
using LiSA_Service.Users.Dto;
using NHibernate;

namespace LiSA_Service.ResearchProjects.Actioners
{
    public class ProjectActioner : BaseActioner
    {
        /// <summary>
        /// Retreives a single Project with the given ID
        /// </summary>
        /// <param name="query">ID of the project</param>
        /// <returns>The corresponding ProjectDTO</returns>
        public Dto.Project GetProjectByID(GetProjectByIDQuery query)
        {
            var theProject = new ResearchProject();
            return NHibernateProvider.Instance.WithSession(
               hsession =>
               {
                   theProject = hsession.Get<ResearchProject>(query.ProjectID);


                   // Failsafe
                   if (theProject == null)
                   {
                       return new Dto.Project();
                   }
                   var projectUsers = new List<User>();
                   foreach (var userID in theProject.Users)
                   {
                       projectUsers.Add(ActionerFactory.Instance.Get<UserActioner>().GetUserByID(userID));
                   }

                   // Build and return
                   var dtoProject = new Dto.Project
                   {
                       ProjectID = theProject.ID,
                       Articles = GetArticles(theProject.Articles.ToList()),
                       Criteria = theProject.Criteria.ToList(),
                       CurrentPhase = theProject.CurrentPhase,
                       ProjectLead = theProject.ProjectLead,
                       Users = projectUsers,
                       Title = theProject.Title,
                       Description = theProject.Description,
                       Stages = theProject.Stages.ToList(),
                       ResearchQuestions = theProject.ResearchQuestions.ToList(),
                       SearchKeywords = theProject.SearchKeyWords.ToList(),
                       SearchStrings = theProject.SearchStrings.ToList(),
                       Settings = theProject.Settings.ToList(),
                       MajorityNumber = theProject.MajorityNumber,
                       RemovedArticles = theProject.RemovedArticles.ToList(),
                   };
                   if (theProject.Stages.Any())
                   {
                       foreach (var stage in theProject.Stages)
                       {
                           NHibernateUtil.Initialize(stage.DecisionPoints);
                       }
                   }

                   return dtoProject;
               });
        }

        /// <summary>
        /// Retreives all projects for a User as a ProjectShortInfo for listing in an overview
        /// </summary>
        /// <param name="query">The query</param>
        /// <returns>All project thhe user has as a ShortInfo</returns>
        public List<Dto.ProjectShortInfo> GetAllProjectsForUserID(ProjectShortinfoQuery query)
        {
            var shortInfos = new List<Dto.ProjectShortInfo>();

            NHibernateProvider.Instance.WithSession(
               hsession =>
               {
                   // Find the projects
                   var projs = hsession.QueryOver<ResearchProject>().List().ToList();
                   var projects = projs.Where(x => x.Users.Contains(query.OwnerID));

                   // Transform the projects
                   foreach (var project in projects)
                   {
                       var leadUser = ActionerFactory.Instance.Get<UserActioner>().GetUserByID(project.ProjectLead);
                       shortInfos.Add(new ProjectShortInfo
                       {
                           ProjectID = project.ID,
                           ProjectLead = leadUser,
                           ProjectTitle = project.Title,
                           ProjectDescription = project.Description
                       });
                   }
               });

            return shortInfos;
        }

        /// <summary>
        /// Creates a new project
        /// </summary>
        /// <param name="command">The CreateProject command</param>
        public void CreateNewProject(CreateProjectCommand command)
        {
            var newProject = new ResearchProject
            {
                ID = Guid.NewGuid(),
                IsActive = true,
                CreatedDateTime = DateTime.Now,
                Title = command.Project.Title,
                Description = command.Project.Description,
                CurrentPhase = 0,
                ProjectLead = command.Project.ProjectLead,
                Stages = new Collection<ProjectStage>(command.Project.Stages ?? new List<ProjectStage>()),
                Users = new Collection<Guid>(command.Project.Users.Select(x => x.ID).ToList()),
                ResearchQuestions = new Collection<ResearchQuestion>(command.Project.ResearchQuestions),
                SearchKeyWords = new Collection<string>(command.Project.SearchKeywords),
                SearchStrings = new Collection<ProjectSearchString>(command.Project.SearchStrings),
                Articles = new Collection<Guid>(),
                ArticlesMasterlist = new Collection<Guid>(),
                Criteria = new Collection<ProjectCriteria>(command.Project.Criteria),
                MajorityNumber = command.Project.MajorityNumber
            };

            // Ensure the ProjectLead is also listed in the userlist. This enables faster searches in projects
            newProject.Users.Add(command.Project.ProjectLead);

            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   foreach (var item in newProject.Stages)
                   {
                       item.ID = Guid.NewGuid();
                       item.CreatedDateTime = DateTime.Now;
                       hsession.Save(item);
                   }
                   foreach (var item in newProject.ResearchQuestions)
                   {
                       item.ID = Guid.NewGuid();
                       item.CreatedDateTime = DateTime.Now;
                       hsession.Save(item);
                   }
                   foreach (var item in newProject.SearchStrings)
                   {
                       item.ID = Guid.NewGuid();
                       item.CreatedDateTime = DateTime.Now;
                       hsession.Save(item);
                   }
                   foreach (var item in newProject.Criteria)
                   {
                       item.ID = Guid.NewGuid();
                       item.CreatedDateTime = DateTime.Now;
                       hsession.Save(item);
                   }
                   hsession.Save(newProject);
               });

            // Serialize the bibtex string
            this.SerializeBibTexFile(new AddBibTexCommand() { ProjectID = newProject.ID, BibTexString = command.Project.RawBibTexString });
        }

        /// <summary>
        /// Updates a project
        /// </summary>
        /// <param name="command">The UpdateProject command</param>
        public void UpdateProject(UpdateProjectCommand command)
        {

            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   // Convert
                   var theProject = hsession.Get<Models.ResearchProject>(command.Project.ProjectID);
                   theProject.Title = command.Project.Title;
                   theProject.Description = command.Project.Description;
                   theProject.ProjectLead = command.Project.ProjectLead;
                   theProject.Stages = new Collection<ProjectStage>(command.Project.Stages ?? new List<ProjectStage>());
                   theProject.Users = new Collection<Guid>(command.Project.Users.Select(x => x.ID).ToList());
                   theProject.ResearchQuestions = new Collection<ResearchQuestion>(command.Project.ResearchQuestions);
                   theProject.SearchKeyWords = new Collection<string>(command.Project.SearchKeywords);
                   theProject.SearchStrings = new Collection<ProjectSearchString>(command.Project.SearchStrings);
                   theProject.Criteria = new Collection<ProjectCriteria>(command.Project.Criteria);
                   theProject.RemovedArticles = command.Project.RemovedArticles;
                   theProject.MajorityNumber = command.Project.MajorityNumber;

                   // Remove articles that are in the RemovedArticles list
                   var keepArticles = new Collection<Guid>();
                   foreach (var article in theProject.Articles)
                   {
                       keepArticles.Add(article);
                   }
                   foreach (var article in theProject.Articles.Where(article => command.Project.RemovedArticles.Any(x => x == article)))
                   {
                       keepArticles.Remove(article);
                   }

                   theProject.Articles = keepArticles;

                   // Update subobjects
                   foreach (var item in theProject.Stages)
                   {
                       if (string.IsNullOrEmpty(item.Description)) { theProject.Stages.Remove(item); continue; }                       

                       if (item.ID == Guid.Empty)
                       {
                           item.ID = Guid.NewGuid();
                           item.CreatedDateTime = DateTime.Now;
                       }
                   }
                   foreach (var item in theProject.ResearchQuestions)
                   {
                       if (string.IsNullOrEmpty(item.Question)) { theProject.ResearchQuestions.Remove(item); continue; }

                       if (item.ID == Guid.Empty)
                       {
                           item.ID = Guid.NewGuid();
                           item.CreatedDateTime = DateTime.Now;
                       }
                   }
                   foreach (var item in theProject.SearchStrings)
                   {
                       if (string.IsNullOrEmpty(item.SearchString)) { theProject.SearchStrings.Remove(item); continue; }

                       if (item.ID == Guid.Empty)
                       {
                           item.ID = Guid.NewGuid();
                           item.CreatedDateTime = DateTime.Now;
                       }
                   }
                   foreach (var item in theProject.Criteria)
                   {
                       if (string.IsNullOrEmpty(item.Description)) { theProject.Criteria.Remove(item); continue; }

                       if (item.ID == Guid.Empty)
                       {
                           item.ID = Guid.NewGuid();
                           item.CreatedDateTime = DateTime.Now;
                       }
                   }

                   // Update
                   hsession.SaveOrUpdate(theProject);
               });

            // Serialize the bibtex string
            this.SerializeBibTexFile(new AddBibTexCommand { ProjectID = command.Project.ProjectID, BibTexString = command.Project.RawBibTexString });
        }

        /// <summary>
        /// Changes the status of a project between active and inactive
        /// </summary>
        /// <param name="command">The command</param>
        public void ChangeProjectStatus(ChangeProjectStatusCommand command)
        {
            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   var theProject = hsession.Get<ResearchProject>(command.ProjectID);
                   if (theProject == null)
                   {
                       return;
                   }
                   theProject.IsActive = command.NewStatus;
                   hsession.Save(theProject);
               });
        }

        /// <summary>
        /// Serializes a BibTex string
        /// </summary>
        /// <param name="command"></param>
        public void SerializeBibTexFile(AddBibTexCommand command)
        {
            var articles = new BibTexParser().ParseBibTex(command.BibTexString);

            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   var theProject = hsession.Get<ResearchProject>(command.ProjectID);
                   foreach (var article in articles)
                   {
                       foreach (var field in article.Fields)
                       {
                           field.ID = Guid.NewGuid();
                           field.CreatedDateTime = DateTime.Now;
                           hsession.Save(field);
                       }
                       article.CreatedDateTime = DateTime.Now;
                       hsession.Save(article);
                       theProject.Articles.Add(article.ID);
                       theProject.ArticlesMasterlist.Add(article.ID);
                   }
                   hsession.Update(theProject);
               });
        }

        /// <summary>
        /// Retreives all articles for a set of IDs and converts to DTO
        /// </summary>
        /// <param name="articleIDs">IDs of articles to fetch</param>
        /// <returns>The articles converted to the corresponding DTO</returns>
        public List<Dto.Article> GetArticles(List<Guid> articleIDs)
        {
            var returnArticles = new List<Dto.Article>();
            if (articleIDs == null || articleIDs.Count < 1) return returnArticles;

            var modelArticles = new List<Models.Article>();
            NHibernateProvider.Instance.WithSession(
                hsession =>
                    modelArticles.AddRange(articleIDs.Select(articleID => hsession.Get<Models.Article>(articleID)))
                    );

            // Convert to DTO
            foreach (var modelArticle in modelArticles)
            {
                var returnArticle = new Dto.Article
                {
                    ArticleText = string.Empty,
                    Author = modelArticle.Author,
                    Keywords = modelArticle.KeyWords,
                    MinedKeyWords = modelArticle.MinedKeyWords,
                    Abstract = modelArticle.Abstract,
                    Database = modelArticle.Database,
                    Domain = modelArticle.Domain,
                    Organization = modelArticle.Organization,
                    Name = modelArticle.Title,
                    PublicationDate = modelArticle.PublicationDate,
                    Type = modelArticle.Type,
                    WordCloud = modelArticle.WordCloud,
                    ID = modelArticle.ID
                };

                // Make sure any faulty articles are not presented to the users
                if (!string.IsNullOrWhiteSpace(returnArticle.Name))
                {
                    returnArticles.Add(returnArticle);
                }
            }

            return returnArticles;
        }

        /// <summary>
        /// Finds all ProjectStages fora  given project
        /// </summary>
        /// <param name="projectID">ID of the project</param>
        /// <returns>All Stages on the project</returns>
        public List<ProjectStage> GetAllStagesForProject(Guid projectID)
        {
            var returnStages = new List<ProjectStage>();
            if (projectID == Guid.Empty) return returnStages;

            NHibernateProvider.Instance.WithSession(
                hsession =>
                    returnStages = hsession.Get<Project>(projectID).Stages
                    );
            return returnStages;
        }

        /// <summary>
        /// Finds all ArticleFeedback for a project for a given user
        /// </summary>
        /// <param name="projectID">ID of the Project</param>
        /// <param name="userID">ID of the user</param>
        /// <returns>A Lsit of the ArticleFeedback found</returns>
        public List<ArticleFeedback> GetAllFeedbackForUser(Guid projectID, Guid userID)
        {
            var returnArticleFeedback = new List<ArticleFeedback>();
            if (userID == Guid.Empty) return returnArticleFeedback;

            NHibernateProvider.Instance.WithSession(
                hsession => {
                    var allFeedback = hsession.QueryOver<ArticleFeedback>().List().ToList();
                    var projectFeedBack = allFeedback.Where(x => x.ProjectID == projectID).ToList();
                    returnArticleFeedback = hsession.QueryOver<ArticleFeedback>().Where(x => x.ProjectID == projectID && x.ReviewerUserID == userID).List().ToList();
                    });
            return returnArticleFeedback;
        }

        /// <summary>
        /// Saves or updates each ArticleFeedback in the list
        /// </summary>
        /// <param name="cmd">List of ArticleFeedback command containing objects to persist</param>
        public void SaveOrUpdateFeedback(SaveOrUpdateArticleFeedbackCommand cmd)
        {
            var feedbacksToSave = new List<ArticleFeedback>();
            var feedbacksToUpdate = new List<ArticleFeedback>();
            NHibernateProvider.Instance.WithSessionAndTransaction(
                hsession =>
                {
                    foreach (var feedback in cmd.ArticleFeedbacks)
                    {
                        // Since an update is called on each radiobutton check but the version IS NOT updated, we need to load the existing version and copy it.
                        var dbFeedback = hsession.Get<ArticleFeedback>(feedback.ID);
                        if (dbFeedback == null)
                        {
                            //See if there is an issue with updating the ID clientside for new feedback to prevent duplicate feedback for a given user+article in a project
                            var potFeedback =
                                hsession.QueryOver<ArticleFeedback>()
                                    .Where(
                                        x =>
                                            x.Stage == feedback.Stage &&
                                            x.ArticleID == feedback.ArticleID &&
                                            x.ReviewerUserID == feedback.ReviewerUserID &&
                                            x.ProjectID == feedback.ProjectID).List();
                            if (potFeedback != null && potFeedback.Any())
                            {
                                dbFeedback = potFeedback.First();
                            }
                        }
                        if (dbFeedback != null)
                        {
                            dbFeedback.OptionSelected = feedback.OptionSelected;
                            hsession.Update(dbFeedback);
                        }
                        else
                        // No feedback matching the ID. Then we need to save it instead.
                        {
                            if (feedback.ID == Guid.Empty)
                            {
                                feedback.ID = Guid.NewGuid();
                            }
                            feedback.CreatedDateTime = DateTime.Now;
                            hsession.Save(feedback);
                        }
                    }
                }
                );
        }

        /// <summary>
        /// Returns a lsit of articleFeedback for a given project
        /// </summary>
        /// <param name="projectID">ID of the project</param>
        /// <returns>All ArticleFeedback in the database for the given project</returns>
        public List<ArticleFeedback> GetAllFeedbackForProject(Guid projectID)
        {
            var returnArticleFeedback = new List<ArticleFeedback>();
            if (projectID == Guid.Empty) return returnArticleFeedback;

            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    returnArticleFeedback = hsession.QueryOver<ArticleFeedback>().Where(x => x.ProjectID == projectID).List().ToList();
                });
            return returnArticleFeedback;
        }

        /// <summary>
        /// Processes a project and removes all articles with enough nay-votes attached
        /// </summary>
        /// <param name="cmd">ID of the project to process</param>
        public void RemoveBadArticles(HandleExcludedArticlesCommand cmd)
        {
            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   var theProject = hsession.Get<ResearchProject>(cmd.ProjectID);

                   // Failsafe
                   if (theProject == null)
                   {
                       return;
                   }

                   // Get the feedback
                   var articleFeedback = new List<ArticleFeedback>();
                   articleFeedback = hsession.QueryOver<ArticleFeedback>().Where(x => x.ProjectID == cmd.ProjectID && x.Stage == 0).List().ToList();
                   // Iterate through the articles and remove dead articles.
                   foreach (var article in theProject.Articles)
                   {
                       // Any feedback at all for the article?
                       if (articleFeedback.Any(x => x.ArticleID == article))
                       {
                           // Yes! Then we can count them! 
                           if (articleFeedback.Count(x => x.ArticleID == article && x.OptionSelected == "Out") >= theProject.MajorityNumber)
                           {
                               // Bad article! Kill it with fire and stuff!
                               theProject.RemovedArticles.Add(article);
                           }
                       }
                   }
                   // Can't modify colelciton while enumerating, but we saved the work in the RemovedARticles!
                   foreach (var article in theProject.RemovedArticles)
                   {
                       if (theProject.Articles.Any(x => x == article))
                       {
                           theProject.Articles.Remove(article);
                       }
                   }

                   // Done, let's save the changes
                   hsession.Update(theProject);
               });
        }

        /// <summary>
        /// Edits and article
        /// </summary>
        /// <param name="cmd">The EditArticleCommand</param>
        public void EditArticle(UpdateArticleCommand cmd)
        {
            NHibernateProvider.Instance.WithSessionAndTransaction(
                hsession =>
                {
                    var article = hsession.Get<Models.Article>(cmd.Article.ID);
                    article.Author = cmd.Article.Author;
                    article.Organization = cmd.Article.Organization;
                    article.Type = cmd.Article.Type;
                    article.Abstract = cmd.Article.Abstract;
                    article.KeyWords = cmd.Article.Keywords;
                    article.Title = cmd.Article.Name;
                    article.Text = cmd.Article.ArticleText;
                    article.MinedKeyWords = cmd.Article.MinedKeyWords;
                    article.WordCloud = cmd.Article.WordCloud;
                    article.PublicationDate = cmd.Article.PublicationDate;
                    article.Domain = cmd.Article.Domain;
                    article.Database = cmd.Article.Database;

                    hsession.Update(article);
                });
        }

        /// <summary>
        /// Generates a bibtex file for 
        /// </summary>
        /// <param name="cmd"></param>
        public GenerateBibtexQueryResult CreateBibTexFromArticles(GenerateBibtexQuery cmd)
        {
            // Get the articles
            var articles = new List<Models.Article>();
            string result = string.Empty;
            NHibernateProvider.Instance.WithSession(
              hsession =>
              {
                  var theProject = hsession.Get<ResearchProject>(cmd.ProjectID);

                  // Failsafe
                  if (theProject == null)
                  {
                      return;
                  }

                  // Get the articles
                  articles.AddRange(theProject.Articles.Select(articleID => hsession.Get<Models.Article>(articleID)));
                  // Generate the bibTex string
                  foreach (var article in articles)
                  {
                      var newString = "@";
                      newString = newString + article.Type + "{" + article.BibtexTitle + "," + Environment.NewLine;
                      newString = newString + "Author" + "{" + article.Author + "}," + Environment.NewLine;
                      newString = newString + "Title" + "{" + article.Title + "}," + Environment.NewLine;
                      newString = newString + "Organization" + "{" + article.Organization + "}," + Environment.NewLine;
                      newString = newString + "Domain" + "{" + article.Domain + "}," + Environment.NewLine;
                      newString = newString + "Abstract" + "{" + article.Abstract + "}," + Environment.NewLine;
                      newString = newString + "PublicationDate" + "{" + article.PublicationDate + "}," + Environment.NewLine;
                      newString = newString + "Database" + "{" + article.Database + "}," + Environment.NewLine;
                      // Build for the remaining fields that are not inherent to the standard article
                      foreach (var field in article.Fields)
                      {
                          newString = newString + field.Type + "{" + field.Value + "}," + Environment.NewLine;
                      }

                      // End the input
                      newString = newString + "}" + Environment.NewLine + Environment.NewLine;
                      result = result + newString;
                  }

              });
            // Return result
            return new GenerateBibtexQueryResult { BibtexFileContents = result, TotalResults = 1 };
        }
    }
}